<!-- Copyright 2015 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// -->
<!DOCTYPE html>
<html>
  <head>
    <title>Integration tests for e2e.otr/</title>
    <script src="../../../../../javascript/closure/base.js"></script>
    <script src="test_js_deps-runfiles.js"></script>
    <script>
    goog.require('e2e.otr.ContextImpl');
    goog.require('e2e.otr.Session');
    goog.require('e2e.otr.constants');
    goog.require('e2e.otr.message.Data');
    goog.require('e2e.otr.message.Message');
    goog.require('e2e.otr.pubkey.Dsa');
    goog.require('goog.crypt');
    goog.require('goog.storage.mechanism.HTML5LocalStorage');
    goog.require('goog.testing.PropertyReplacer');
    goog.require('goog.testing.asserts');
    goog.require('goog.testing.jsunit');
    </script>
    <script>
    var constants = e2e.otr.constants;
    var AUTHSTATE = constants.AUTHSTATE;
    var MSGSTATE = constants.MSGSTATE;

    var stubs;
    var persistentStubs = new goog.testing.PropertyReplacer();

    var lastSent;

    persistentStubs.setPath('e2e.otr.Session.prototype.send', function(data) {
      console.log(data);
      if (data instanceof e2e.otr.message.Message) {
        data = data.prepareSend();
      }
      lastSent = data;
    });

    var storageLocal = createStorage();
    persistentStubs.setPath('goog.storage.mechanism.HTML5LocalStorage', function() {
      return storageLocal;
    });

    var localKey = {
      p: goog.crypt.hexToByteArray('fd7f53811d75122952df4a9c2eece4e7f611b7523ce' +
      'f4400c31e3f80b6512669455d402251fb593d8d58fabfc5f5ba30f6cb9b556cd7813b801' +
      'd346ff26660b76b9950a5a49f9fe8047b1022c24fbba9d7feb7c61bf83b57e7c6a8a6150' +
      'f04fb83f6d3c51ec3023554135a169132f675f3ae2b61d72aeff22203199dd14801c7'),
      q: goog.crypt.hexToByteArray('9760508f15230bccb292b982a2eb840bf0581cf5'),
      g: goog.crypt.hexToByteArray('f7e1a085d69b3ddecbbcab5c36b857b97994afbbfa3' +
      'aea82f9574c0b3d0782675159578ebad4594fe67107108180b449167123e84c281613b7c' +
      'f09328cc8a6e13c167a8b547c8d28e0a3ae1e2bb3a675916ea37f0bfa213562f1fb627a0' +
      '1243bcca4f1bea8519089a883dfe15ae59f06928b665e807b552564014c3bfecf492a'),
      x: goog.crypt.hexToByteArray('30861dffeacc056e582fa32ce14480dd13484f34'),
      y: goog.crypt.hexToByteArray('3f8ee5db18f8eebff022a9697672c333088decae904' +
      'd07f1a739e2944674a5e699fa188e74c6060a28c2ba222ffe0c0c30d74a9182de8789a7a' +
      '05111b7bc88b78a9a60ef9b5501e99786901fac2f3b7f045dca098f49604cf9a636c779e' +
      'a49a3240a9f960c258263040b04b5e9fe4e3c42a18086128271d4972ac68d90381bbf')
    };
    var localContext = createContext(localKey);
    var localSession =
        new e2e.otr.Session(localContext, new Uint8Array([1, 1, 1, 1]));

    var storageRemote = createStorage();
    persistentStubs.setPath('goog.storage.mechanism.HTML5LocalStorage', function() {
      return storageRemote;
    });
    var remoteKey = {
      p: goog.crypt.hexToByteArray('fd7f53811d75122952df4a9c2eece4e7f611b7523ce' +
      'f4400c31e3f80b6512669455d402251fb593d8d58fabfc5f5ba30f6cb9b556cd7813b801' +
      'd346ff26660b76b9950a5a49f9fe8047b1022c24fbba9d7feb7c61bf83b57e7c6a8a6150' +
      'f04fb83f6d3c51ec3023554135a169132f675f3ae2b61d72aeff22203199dd14801c7'),
      q: goog.crypt.hexToByteArray('9760508f15230bccb292b982a2eb840bf0581cf5'),
      g: goog.crypt.hexToByteArray('f7e1a085d69b3ddecbbcab5c36b857b97994afbbfa3' +
      'aea82f9574c0b3d0782675159578ebad4594fe67107108180b449167123e84c281613b7c' +
      'f09328cc8a6e13c167a8b547c8d28e0a3ae1e2bb3a675916ea37f0bfa213562f1fb627a0' +
      '1243bcca4f1bea8519089a883dfe15ae59f06928b665e807b552564014c3bfecf492a'),
      x: goog.crypt.hexToByteArray('6cc79680373c932e88827f06af036a5dfd4de65d'),
      y: goog.crypt.hexToByteArray('d25e344833a2541c159fc2e98a74ba119c60ae13d2f' +
      'bf6ac505d25c38f82886b40422c44c43ce11404d664dc75c4124ae16cda56919a9dbc75b' +
      '705d71b615d8542290654705d0c9e909b0729a28face053f6726a110ab90e8f39c37e452' +
      'e2341d356a0555262df80dfa8caa420abc4d67edad0d25ee3cb8d1917e633761997eb')
    };
    var remoteContext = createContext(remoteKey);
    var remoteSession =
        new e2e.otr.Session(remoteContext, new Uint8Array([2, 2, 2, 2]));

    function setUp() {
      stubs = new goog.testing.PropertyReplacer();
    }

    function tearDown() {
      stubs.reset();
    }

    function createStorage() {
      var storage = {};
      var get = function(key) {
        return storage[key];
      };
      var set = function(key, value) {
        storage[key] = value;
      };
      return {get: get, set: set};
    }

    function createContext(key) {
      var storage = goog.storage.mechanism.HTML5LocalStorage();
      storage.set('PRIVKEY', JSON.stringify(key));
      storage.set('PUBKEY', JSON.stringify(new e2e.otr.pubkey.Dsa(key).pack()));
      return new e2e.otr.ContextImpl();
    }

    function testAKE() {
      assertEquals(MSGSTATE.PLAINTEXT, localSession.getMsgState());
      assertEquals(AUTHSTATE.NONE, localSession.getAuthState());
      assertEquals(MSGSTATE.PLAINTEXT, remoteSession.getMsgState());
      assertEquals(AUTHSTATE.NONE, remoteSession.getAuthState());

      // Start OTR by sending DH_COMMIT to remote.
      localSession.initiateOtr();

      assertEquals(MSGSTATE.PLAINTEXT, localSession.getMsgState());
      assertEquals(AUTHSTATE.AWAITING_DHKEY, localSession.getAuthState());
      assertEquals(MSGSTATE.PLAINTEXT, remoteSession.getMsgState());
      assertEquals(AUTHSTATE.NONE, remoteSession.getAuthState());

      // Remote receives receives DH_COMMIT, sends back DH_KEY.
      remoteSession.processMessage(lastSent);

      assertEquals(MSGSTATE.PLAINTEXT, localSession.getMsgState());
      assertEquals(AUTHSTATE.AWAITING_DHKEY, localSession.getAuthState());
      assertEquals(MSGSTATE.PLAINTEXT, remoteSession.getMsgState());
      assertEquals(AUTHSTATE.AWAITING_REVEALSIG, remoteSession.getAuthState());

      // Receive DH_KEY, send REVEAL_SIGNATURE.
      localSession.processMessage(lastSent);

      assertEquals(MSGSTATE.PLAINTEXT, localSession.getMsgState());
      assertEquals(AUTHSTATE.AWAITING_SIG, localSession.getAuthState());
      assertEquals(MSGSTATE.PLAINTEXT, remoteSession.getMsgState());
      assertEquals(AUTHSTATE.AWAITING_REVEALSIG, remoteSession.getAuthState());

      // Remote receives REVEAL_SIGNATURE, sends back SIGNATURE.
      remoteSession.processMessage(lastSent);

      assertEquals(MSGSTATE.PLAINTEXT, localSession.getMsgState());
      assertEquals(AUTHSTATE.AWAITING_SIG, localSession.getAuthState());
      assertEquals(MSGSTATE.ENCRYPTED, remoteSession.getMsgState());
      assertEquals(AUTHSTATE.NONE, remoteSession.getAuthState());

      // Receive SIGNATURE, enter MSGSTATE_ENCRYPTED.
      localSession.processMessage(lastSent);

      assertEquals(MSGSTATE.ENCRYPTED, localSession.getMsgState());
      assertEquals(AUTHSTATE.NONE, localSession.getAuthState());
      assertEquals(MSGSTATE.ENCRYPTED, remoteSession.getMsgState());
      assertEquals(AUTHSTATE.NONE, remoteSession.getAuthState());
    }

    // This test must happen after testAKE.
    function testDataMessage() {
      var called = false;
      var plaintext = 'hello';

      stubs.setPath('remoteSession.display', function() {
        called = true;
        assertArrayEquals(Array.apply([], arguments), [plaintext]);
      });

      localSession.send(new e2e.otr.message.Data(localSession, plaintext));
      remoteSession.processMessage(lastSent);

      assertTrue(called);
    }

    function testInteractive() {
      var bindIo = function(input, history, session, otherSession) {
        var logHistory = function(data, us) {
          var div = document.createElement('div');
          div.classList.add(us ? 'us' : 'them');
          div.innerText = data;
          history.appendChild(div);
          history.scrollTop = history.scrollHeight;
        };

        input.addEventListener('keydown', function(e) {
          if (e.keyCode == 13) {
            session.send(new e2e.otr.message.Data(session, this.value));
            otherSession.processMessage(lastSent);
            logHistory(this.value, true);
            this.value = '';
          }
        });
        persistentStubs.replace(session, 'display', function(data) {
          logHistory(data, false);
        });
      };

      bindIo(document.getElementById('localInput'),
          document.getElementById('localHistory'),localSession, remoteSession);
      bindIo(document.getElementById('remoteInput'),
          document.getElementById('remoteHistory'), remoteSession, localSession);

      Array.apply([], document.querySelectorAll('.chat-input')).forEach(function(e) {
        e.removeAttribute('disabled');
      });
    }
    </script>
    <style type="text/css">
      * {
        box-sizing: border-box;
      }
      .chat-interface {
        display: inline-block;
        width: calc(50% - 20px);
        height: 600px;
        font-size: 0;
        margin: 10px;
      }
      .chat-interface > * {
        font-size: initial;
      }
      .chat-history {
        width: 100%;
        height: 570px;
        padding: 20px;
        border: 1px solid black;
        overflow: auto;
      }
      .chat-history > div {
        color: white;
        border-radius: 10px;
        padding: 5px 10px;
        margin: 5px;
      }
      .chat-history .us {
        background: blue;
        float: left;
        clear: both;
      }
      .chat-history .them {
        background: red;
        float: right;
        clear: both;
      }
      .chat-input {
        width: 100%;
        height: 30px;
      }
    </style>
  </head>
  <body>
    <div class="chat-interface">
      <div id="localHistory" class="chat-history"></div>
      <input id="localInput"class="chat-input" disabled />
    </div><div class="chat-interface">
      <div id="remoteHistory" class="chat-history"></div>
      <input id="remoteInput"class="chat-input" disabled />
    </div>
  </body>
</html>
